package com.aratana.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;

import com.aratana.ui.fields.BasicField;
import com.aratana.ui.fields.CheckField;
import com.aratana.ui.fields.ComboField;
import com.aratana.ui.fields.DateField;
import com.aratana.ui.fields.DecimalField;
import com.aratana.ui.fields.LookupField;
import com.aratana.ui.fields.LookupSelector;
import com.aratana.ui.fields.MoneyField;
import com.aratana.ui.fields.NumberField;
import com.aratana.ui.fields.PasswordField;
import com.aratana.ui.fields.StringField;
import com.aratana.ui.fields.TableField;
import com.aratana.ui.table.ObjectTableColumnSpec;

public class FormBuilder {

	private int line = 0;
	private int column = 0;
	private int lineSize = 1;
	private int columnSize = 1;

	private int width = -1;
	private int height = -1;

	private int max = -1;

	private int maxColumn = 0;

	private boolean spanH = false;
	private boolean spanV = false;

	private boolean caseSensitive = false;

	private boolean enabled = true;
	private boolean editable = true;

	private InputChangeListener inputChanceListener;

	private InternalPanel panel = new InternalPanel();

	private String name;

	boolean getted = false;

	public FormBuilder limit(int limit) {
		this.max = limit;
		return this;
	}

	public FormBuilder name(String name) {
		this.name = name;
		return this;
	}

	public FormBuilder size(int width, int height) {
		return width(width).height(height);
	}

	public FormBuilder width(int width) {
		this.width = width;
		return this;
	}

	public FormBuilder height(int height) {
		this.height = height;
		return this;
	}

	public FormBuilder span() {
		return span(false);
	}

	public FormBuilder span(boolean vertical) {
		spanH = true;
		spanV = vertical;
		return this;
	}

	public FormBuilder caseSensitive() {
		caseSensitive = true;
		return this;
	}

	public FormBuilder enabled(boolean enabled) {
		this.enabled = enabled;
		return this;
	}

	public FormBuilder editable(boolean editable) {
		this.editable = editable;
		return this;
	}

	public FormBuilder alloc(int columns) {
		return alloc(1, columns);
	}

	public FormBuilder alloc(int lines, int columns) {
		lineSize = lines;
		columnSize = columns;
		return this;
	}

	public FormBuilder down() {
		return down(lineSize);
	}

	public FormBuilder down(int size) {
		line += size;
		column = 0;
		return this;
	}

	public FormBuilder up() {
		line -= lineSize;
		if (line < 0) {
			line = 0;
		}
		column = 0;
		return this;
	}

	public FormBuilder right() {
		return right(columnSize);
	}

	public FormBuilder right(int columns) {
		column += columns;
		if (column > maxColumn) {
			maxColumn = column;
		}
		return this;
	}

	public FormBuilder left() {
		return left(columnSize);
	}

	public FormBuilder left(int columns) {
		column -= columns;
		if (column < 0) {
			column = 0;
		}
		return this;
	}

	public FormBuilder listener(InputChangeListener listener) {
		inputChanceListener = listener;
		return this;
	}

	public FormBuilder cleanListener() {
		inputChanceListener = null;
		return this;
	}

	public FormBuilder label(String label) {
		return custom(new JLabel(label, JLabel.RIGHT));
	}

	public FormBuilder number() {
		return custom(new NumberField());
	}

	public FormBuilder decimal() {
		return custom(new DecimalField());
	}

	public FormBuilder money() {
		return custom(new MoneyField());
	}

	public FormBuilder date() {
		return custom(new DateField(true, true, false));
	}

	public FormBuilder string() {
		return string(null);
	}

	public FormBuilder string(String text) {
		StringField field = new StringField();
		field.setText(text);
		return custom(field);
	}

	public FormBuilder action(String text) {
		JButton button = new JButton(text);
		return custom(button);
	}

	public FormBuilder action(Icon icon) {
		JButton button = new JButton(icon);
		return custom(button);
	}

	public FormBuilder check() {
		CheckField check = new CheckField();
		return custom(check);
	}

	public <T> FormBuilder combo(Class<T> comboClass) {
		ComboField<T> combo = new ComboField<T>(comboClass);
		if (comboClass.isEnum()) {
			for (T t : comboClass.getEnumConstants()) {
				combo.add(t);
			}
		}
		return custom(combo);
	}

	public <T> FormBuilder combo(T value, Class<T> comboClass) {
		ComboField<T> combo = new ComboField<T>(comboClass);
		combo.add(value);
		if (comboClass.isEnum()) {
			for (T t : comboClass.getEnumConstants()) {
				combo.add(t);
			}
		}
		return custom(combo);
	}

	public <T> FormBuilder combo(Class<T> comboClass, T... values) {
		ComboField<T> combo = new ComboField<T>(comboClass);
		for (T t : values) {
			combo.add(t);
		}
		return custom(combo);
	}

	public <T> FormBuilder combo(Class<T> comboClass, List<T> values) {
		ComboField<T> combo = new ComboField<T>(comboClass);
		for (T t : values) {
			combo.add(t);
		}
		return custom(combo);
	}

	public <T> FormBuilder combo(Class<T> comboClass, T value, List<T> values) {
		ComboField<T> combo = new ComboField<T>(comboClass);
		combo.add(value);
		for (T t : values) {
			combo.add(t);
		}
		return custom(combo);
	}

	public FormBuilder password() {
		PasswordField password = new PasswordField();
		return custom(password);
	}

	public <T> FormBuilder table(Class<T> clazz, ObjectTableColumnSpec... spec) {
		TableField<T> table = new TableField<T>(clazz);
		for (ObjectTableColumnSpec s : spec) {
			table.addColumn(s);
		}
		table.addEmptyColumn();
		return custom(table);
	}

	public <T> FormBuilder lookupByString(Class<T> fieldClass, LookupSelector<T> selector) {
		LookupField<T, String> lookup = new LookupField<T, String>(fieldClass, new StringField(), selector);
		return custom(lookup);
	}

	public FormBuilder custom(Component component) {
		if (width >= 0) {
			component.setPreferredSize(new Dimension(width, component.getPreferredSize().height));
		}
		if (height >= 0) {
			component.setPreferredSize(new Dimension(component.getPreferredSize().width, height));
		}

		component.setName(name);

		if (component instanceof BasicField<?>) {
			if (max > 0) {
				((BasicField<?>) component).setMax(max);
			}
			((BasicField<?>) component).setCaseSensitive(caseSensitive);
			if (inputChanceListener != null) {
				((BasicField<?>) component).addInputChangeListener(inputChanceListener);
			}
		}

		component.setEnabled(enabled);

		if (component instanceof JTextComponent) {
			((JTextComponent) component).setEditable(editable);
		}

		panel.add(component, new InternalConstraints(columnSize, lineSize, spanH ? 100 : 0, spanV ? 100 : 0, InternalConstraints.NORTHWEST, InternalConstraints.BOTH, new Insets(1, 2, 1, 2)));

		right();

		columnSize = lineSize = 1;
		width = height = max = -1;
		spanH = spanV = caseSensitive = false;
		name = null;
		enabled = true;
		editable = true;

		return this;
	}

	public FormBuilder separator() {
		panel.add(new JSeparator(), new InternalConstraints(maxColumn - column, lineSize, 100, 0, InternalConstraints.NORTHWEST, InternalConstraints.BOTH, new Insets(1, 2, 1, 2)));
		return right();
	}

	public JPanel getPanel() {
		if (!getted) {
			getted = true;
			panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
			return panel;
		} else {
			throw new RuntimeException("Panel is aready getted.");
		}
	}

	@SuppressWarnings("serial")
	private static final class InternalPanel extends JPanel {
		private boolean initialized = false;

		public InternalPanel() {
			super(new GridBagLayout());
			initialized = true;
		}

		@Override
		public void setLayout(LayoutManager mgr) {
			if (initialized) {
				throw new RuntimeException("Operation not supported.");
			} else {
				super.setLayout(mgr);
			}
		}

		@Override
		protected void addImpl(Component comp, Object constraints, int index) {
			if (constraints != null) {
				if (constraints instanceof InternalConstraints) {
					super.addImpl(comp, constraints, index);
				} else {
					throw new RuntimeException("Constraint is not a EasyGridBagPanel constraint :" + constraints.getClass());
				}
			} else {
				throw new NullPointerException("No constraints.");
			}
		}
	}

	public JButton getAction(String name) {
		return (JButton) getComponentByName(name);
	}

	public StringField getString(String name) {
		return (StringField) getComponentByName(name);
	}

	public CheckField getCheck(String name) {
		return (CheckField) getComponentByName(name);
	}

	public ComboField<?> getCombo(String name) {
		return (ComboField<?>) getComponentByName(name);
	}

	@SuppressWarnings("unchecked")
	public <T> ComboField<T> getCombo(String name, Class<T> type) {
		return (ComboField<T>) getComponentByName(name);
	}

	public NumberField getNumber(String name) {
		return (NumberField) getComponentByName(name);
	}

	public DecimalField getDecimal(String name) {
		return (DecimalField) getComponentByName(name);
	}

	public PasswordField getPassword(String name) {
		return (PasswordField) getComponentByName(name);
	}

	@SuppressWarnings("unchecked")
	public <T> TableField<T> getTable(String name) {
		return (TableField<T>) getComponentByName(name);
	}

	@SuppressWarnings("unchecked")
	public <T> T getCustomByName(Class<T> clazz, String name) {
		return (T) getComponentByName(name);
	}

	public Component getComponentByName(String name) {
		return getComponentByName(panel, name);
	}

	private Component getComponentByName(Container parent, String name) {
		Component comp = null;
		for (Component c : parent.getComponents()) {
			if (name.equals(c.getName())) {
				comp = c;
				break;
			} else if (c instanceof Container) {
				comp = getComponentByName((Container) c, name);
				if (comp != null) {
					break;
				}
			}

		}

		return comp;
	}

	@SuppressWarnings("serial")
	private final class InternalConstraints extends GridBagConstraints {
		private InternalConstraints(int gridwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets) {
			super(column, line, gridwidth, gridheight, weightx, weighty, anchor, fill, insets, 0, 0);
		}
	}

	public static class Pessoa {
		String nome;
		Integer idade;

		public Pessoa(String nome, Integer idade) {
			this.nome = nome;
			this.idade = idade;
		}

		public String getNome() {
			return nome;
		}

		public Integer getIdade() {
			return idade;
		}
	}

	public static class PessoaSelector implements LookupSelector<Pessoa> {

		@Override
		public ObjectTableColumnSpec[] columnsSpecs() {
			return new ObjectTableColumnSpec[] { new ObjectTableColumnSpec("Nome", "nome", 200, false, false), new ObjectTableColumnSpec("Idade", "idade", 100, true, false), };
		}

		@Override
		public List<Pessoa> lookup(String cd, String filter) {
			return Arrays.asList(new Pessoa("Dyorgio", 25), new Pessoa("Janaina", 28), new Pessoa("Alcemar", 48), new Pessoa("Raphael Victor", 12), new Pessoa("Susy", 5));
		}

	}

	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getInstalledLookAndFeels()[1].getClassName());
		} catch (Exception e) {
			e.printStackTrace();
		}

		final FormBuilder easyGridPanel = new FormBuilder();

		easyGridPanel //
				.label("Number(4)").alloc(4).limit(4).span().number().down() //
				.label("String").string().width(200).label("String").span().string().string().down() //
				.label("Password").name("password").password().down()//
				.label("Lookup").lookupByString(Pessoa.class, new PessoaSelector()).down()//
				.right(4).name("action").action("Ok").down();

		easyGridPanel.getAction("action").addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				System.err.println(easyGridPanel.getPassword("password").getValue() == null ? null : new String(easyGridPanel.getPassword("password").getValue()));
			}
		});

		JFrame frame = new JFrame("EasyGridPanel");
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.add(easyGridPanel.getPanel());
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);
	}
}
